# -*- coding: utf-8 -*-

import os
import sys

if __name__ == "__main__":
    currentdir = os.path.dirname( os.path.abspath( sys.argv[0] ) )
    basedir = os.path.abspath( os.path.join( currentdir, ".." ) )
    pythondir = basedir
    sys.path.insert( 0, pythondir )
    pass

from logger import get_file_logger

import random
from datetime import timedelta
import traceback
from picklerpc.msghandler import RequestHandler
from picklerpc.sendmsg import sendmsg
from picklerpc import Server
from time import sleep, time
from service import default_request_timeout,default_niceness_level

__all__ = ['Sqlite3TableHandler', 'Sqlite3TableServer', 'Sqlite3TableManager', 'Sqlite3TableRequest']

#---------------------------------------------

class Sqlite3TableHandler(RequestHandler):
    """
    Do some tasks, maybe in subprocess
    """
    
    def _request_handle(self, data):
        """
        @param data: simple object data
        @type: dict
        
        @return: python simple object data
        @rtype: mixed
        """
        result = False
        
        if not (type(data) is dict):
            return result

        if data["task"] == "ping":
            return self.server.ping()
        if data["task"] == "quit":
            return self.server.quit()
        if data["task"] == "query":
            data = data["data"]
            return self.server.query(
                data["name"],
                data["data"],
                data["wait"]
            )
        if data["task"] == "has-result":
            data = data["data"]
            return self.server.has_result(
                data["request-id"]
            )
        if data["task"] == "get-result":
            data = data["data"]
            return self.server.get_result(
                data["request-id"]
            )
        if data["task"] == "get-result-row":
            data = data["data"]
            return self.server.get_result_row(
                data["request-id"]
            )
        return result
    
    pass

#---------------------------------------------

class Sqlite3TableServer(Server):

    table = None

    queue = None
    stop_event = None
    _worker_thread = None

    _query_results = None
    _query_results_lock = None

    _request_id = 0
    _request_id_lock = None

    log = None

    def get_next_request_id(self):
        self._request_id_lock.acquire()
        self._request_id += 1
        req_id = str(self._request_id)
        self._request_id_lock.release()
        return req_id

    def _worker ( self ):

        self.log.info("START: %s" % (self.server_address,))

        from project.sqlite3_raw import Project

        p = Project()

        t = p.getTable(self.table)

        while not self.stop_event.isSet():

            try:
                data = self.queue.get_nowait()
            except Exception:
                data = None

            if data is None:
                sleep(0.1)
                continue

            request_id, query_name, query_data = data

            try:
                result = getattr(t, query_name)(*query_data)
            except Exception as e:
                result = None
                self.log.error("ERROR: %s" % (e,))
                self.log.debug("ERROR: %s\n%s" % (e, traceback.format_exc()))
                pass

            self._query_results_lock.acquire()
            if request_id in self._query_results:
                self._query_results[ request_id ]["result"] = result
                self._query_results[ request_id ]["done"] = True
                if not self._query_results[ request_id ]["wait"]:
                    del self._query_results[ request_id ]
            self._query_results_lock.release()

            self.queue.task_done()

        self.log.info("STOP: %s" % (self.server_address,))

        self.stop_event.clear()
        return


    def server_activate(self):
        try:
            # Python 3
            from queue import Queue
        except ImportError:
            # Python 2
            from Queue import Queue

        from threading import Thread, Event, Lock

        self.log = get_file_logger("Sqlite3TableServer(%s)" % self.table)

        self._query_results = {}
        self._query_results_lock = Lock()

        self._request_id = 0
        self._request_id_lock = Lock()

        self.queue = Queue()

        self.stop_event = Event()
        self._worker_thread = Thread(target=self._worker)
        self._worker_thread.start()

        return Server.server_activate(self)

    def ping(self):
        return "pong"

    def query(self, name, data, wait=True):

        request_id = self.get_next_request_id()

        self._query_results_lock.acquire()
        self._query_results[ request_id ] = {
            "wait" : wait,
            "result" : None,
            "done" : False
        }
        self._query_results_lock.release()

        self.queue.put_nowait((request_id, name, data,))
        return request_id

    def has_result(self, request_id):
        self._query_results_lock.acquire()
        has = (request_id in self._query_results) and (self._query_results[ request_id ]["done"] == True)
        self._query_results_lock.release()
        return has

    def get_result(self, request_id):
        self._query_results_lock.acquire()
        result = self._query_results[ request_id ]["result"]
        del self._query_results[ request_id ]
        self._query_results_lock.release()
        return result

    def get_result_row(self, request_id):
        self._query_results_lock.acquire()

        if request_id in self._query_results:
            result = self._query_results[ request_id ]["result"]
        else:
            result = None
        if type(result) in (list, tuple,):
            if len(result) > 1:
                data = result[1:]
                result = result[0]
                self._query_results[ request_id ]["result"] = data
            elif len(result) <= 1:
                del self._query_results[ request_id ]
                if len(result):
                    result = result[0]
                else:
                    result = None
        else:
            if request_id in self._query_results:
                del self._query_results[ request_id ]
        self._query_results_lock.release()
        return result

    def quit(self):
        self.log.info("quit event")

        # Clean Queue

        while True:
            try:
                data = self.queue.get_nowait()
            except Exception:
                data = None

            if data is None:
                break

            self.queue.task_done()

        import signal

        self.log.debug("send SIGINT to self")
        os.kill( os.getpid(), signal.SIGINT )

        return True


    def terminate_self(self, signum, frame):
        self.log.debug("get SHUTDOWN signal")
        t_begin = time()
        self.stop_event.set()
        self.stop_event.wait()
        self.server_close()
        t_end = time()
        self.log.info("SHUTDOWN in %s sec" % (timedelta(seconds=t_end - t_begin),))
        return

    pass

#---------------------------------------------


class Sqlite3TableRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def ping(self, table):
        return self.manager.sendmsg(table, "ping", {}, 0.1)

    def query(self, table, name, data, wait=True):
        return self.manager.sendmsg(table, "query", {
            "name" : name,
            "data" : data,
            "wait" : wait
        })

    def has_result(self, table, result_id):
        return self.manager.sendmsg(table, "has-result", {
            "request-id" : result_id
        }, 0.1)

    def get_result(self, table, result_id):
        return self.manager.sendmsg(table, "get-result", {
            "request-id" : result_id
        })

    def get_result_row(self, table, result_id):
        return self.manager.sendmsg(table, "get-result-row", {
            "request-id" : result_id
        })

    def quit(self, table):
        return self.manager.sendmsg(table, "quit", {}, 0.1)

    pass


#---------------------------------------------

class Sqlite3TableManager(object):

    host = "127.0.1.9"
    port_min = 10000
    port_max = 20000
    request = None

    ports = None

    log = None

    def __init__(self):
        self.ports = {}
        self.log = get_file_logger("Sqlite3TableManager")
        pass

    def start_service(self, table):

        runing = False
        if self.ports.get(table) is None:

            port = random.randint(self.port_min, self.port_max)

            address = (self.host, port,)

            self.ports[table] = port

            from archive import which
            from subprocess import Popen

            Popen([which("python3"), "service/sqlite3_table.py",
                   "--host", "%s" % self.host,
                   "--port", "%s" % port,
                   "--table", table
            ])

            # WAIT WHILE SERVICE STARTS

            pings = 100
            t_begin = time()
            while not runing:

                pong = self.get_request().ping(table)

                if pong == "pong":
                    runing = True

                pings -= 1
                if pings <= 0:
                    break

            if pings <= 0:
                del self.ports[table]
                self.log.error("FAIL TO START service(%r) on %r in %s sec" % (
                    table,
                    address, timedelta(seconds=time() - t_begin),))
            else:
                self.log.info("STARTED service(%r) on %r in %s sec" % (
                    table,
                    address, timedelta(seconds=time() - t_begin),))

        return runing

    def sendmsg(self, table, task, data, timeout=default_request_timeout):
        port = self.ports[table]
        SERVER_ADDRESS = (self.host, port,)
        try:
            result = sendmsg( SERVER_ADDRESS, {"task":task, "data":data}, timeout=timeout )
        except Exception as e:
            result = None
            self.log.error("SOCKET: %s" % (e,))
            self.log.debug("FAIL SEND MESSAGE: task=%s, data=%r" % ( task, data,))
        return result

    def quit_all(self):
        result = True
        ports = self.ports.copy()
        for table, port in ports.items():
            ret = self.sendmsg(table, "quit", {}, 0.1)
            if ret is None:
                result = False
            else:
                del self.ports[ table ]
        return result

    def ping_all(self):
        result = False
        ports = self.ports.copy()
        for table, port in ports.items():
            ret = self.get_request().ping(table)
            if ret == "pong":
                result = True
        return result

    def get_request(self):
        if not self.request:
            self.request = Sqlite3TableRequest(self)
        return self.request

    pass


#---------------------------------------------


def run_service(host, port, table):

    if hasattr(os, "nice"):
        os.nice(default_niceness_level)

    server = None
    e_code = 0
    try:
        SERVER_ADDRESS = (host, int(port),)

        server = Sqlite3TableServer(SERVER_ADDRESS, Sqlite3TableHandler, bind_and_activate=False)
        server.table = table

        import signal

        signal.signal(signal.SIGINT, server.terminate_self)

        server.server_bind()
        server.server_activate()
        server.serve_forever()
    except Exception as e:
        e_code = 1
        if server and server.log:
            server.log.error("run_service: ERROR=%r" % (e,))
        pass

    return e_code

#---------------------------------------------


def launch():

    from argparse import ArgumentParser

    parser = ArgumentParser(description='Run sqlite3 table service.')
    parser.add_argument('--host', dest='host', action='store', type=str,
        help='Listen on TCP address')
    parser.add_argument('--port', dest='port', action='store', type=int,
        help='Listen on TCP port')
    parser.add_argument('--table', dest='table', action='store', type=str,
        help='Work with table')

    args = parser.parse_args()
    return run_service(args.host, args.port, args.table)

if __name__ == "__main__":
    sys.exit(launch())
